﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Threading;
using System.Text.RegularExpressions;
using System.Timers;

namespace Dasaar.Interaction.Messaging
{
    public class MessagingManager
    {
        private SerialPort serialPort;
        private System.Timers.Timer pollTimer;

        private bool activityRunning;

        private string NewLine = "\r\n";
        private string CtrlZ = ((char)26).ToString();

        public delegate void MessageReceivedHandler(SMSMessage message);
        public event MessageReceivedHandler MessageReceived;

        private static string receivedData;

        protected virtual void OnMessageReceived(SMSMessage message) 
        {
            if (MessageReceived != null)
            {
                MessageReceived(message);
            }
        }

        private void Send(string data)
        {
            serialPort.Write(data);
            serialPort.Write(NewLine);
        }

        private void SendRaw(string data)
        {
            serialPort.Write(data);
        }
        private bool WaitForResponse()
        {
            for (int i = 0; i < 5; i++)
            {
                if (IsSuccess()) return true;
                if (IsError()) return false;
                Thread.Sleep(1000);
            }
            return false;
        }


        private bool SendAndWaitForResponse(string data, bool raw = false)
        {
            ClearResponse();
            if (raw) SendRaw(data); else Send(data);
            bool success = WaitForResponse();
            return success;
        }

        private string ReadResponse()
        {
            string tempData = receivedData;
            receivedData = string.Empty;
            return tempData;
        }

        private void ClearResponse()
        {
            receivedData = string.Empty;
        }

        private bool IsSuccess()
        {
            return (receivedData.IndexOf(NewLine + "OK" + NewLine) > -1 || receivedData.EndsWith("> ")) ? true : false;
        }

        private bool IsLocalError()
        {
            return (receivedData.IndexOf(NewLine + "ERROR" + NewLine) > -1) ? true : false;
        }
        
        private bool IsNetworkError()
        {
            return (receivedData.IndexOf(NewLine + "+CMS ERROR:") > -1) ? true : false;
        }

        private bool IsError()
        {
            return (IsNetworkError() || IsLocalError()) ? true : false;
        }

        private Exception CreateNewException(string error)
        {
            return new MessagingException(string.Format("A communications error occured: {0}", error.Replace("\n", "").Replace("\r", "")));
        }

        public void SendMessage(string number, string message)
        {
            activityRunning = true;
            PDUMessage pduMsg = PDUEncoder.GetPDUMessage(number, message, string.Empty);

            if (!SendAndWaitForResponse(string.Format("AT+CMGF=0"))) throw CreateNewException(ReadResponse());
            if (!SendAndWaitForResponse(string.Format("AT+CMGS={0}", pduMsg.Length))) throw CreateNewException(ReadResponse());
            if (!SendAndWaitForResponse(string.Format("{0}{1}", pduMsg.Content, CtrlZ), true)) throw CreateNewException(ReadResponse());
            activityRunning = false;
        }

        public string GetManufacturer()
        {
            activityRunning = true;
            if (!SendAndWaitForResponse(string.Format("AT+CGMI"))) throw CreateNewException(ReadResponse());
            string response = ReadResponse().Replace(NewLine + "OK" + NewLine, "");
            activityRunning = false;
            return response;
        }

        public string GetModel()
        {
            activityRunning = true;
            if (!SendAndWaitForResponse(string.Format("AT+CGMM"))) throw CreateNewException(ReadResponse());
            string response = ReadResponse().Replace(NewLine + "OK" + NewLine, "");
            activityRunning = false;
            return response;
        }

        public string GetRevision()
        {
            activityRunning = true;
            if (!SendAndWaitForResponse(string.Format("AT+CGMR"))) throw CreateNewException(ReadResponse());
            string response = ReadResponse().Replace(NewLine + "OK" + NewLine, "");
            activityRunning = false;
            return response;
        }

        public string GetPINStatus()
        {
            activityRunning = true;
            if (!SendAndWaitForResponse(string.Format("AT+CPIN?"))) throw CreateNewException(ReadResponse());
            string response = ReadResponse().Replace(NewLine + "OK" + NewLine, "");
            activityRunning = false;
            return response;
        }

        public string SendPIN(string pin)
        {
            activityRunning = true;
            if (!SendAndWaitForResponse(string.Format("AT+CPIN={0}", pin))) throw CreateNewException(ReadResponse());
            string response = ReadResponse().Replace(NewLine + "OK" + NewLine, "");
            activityRunning = false;
            return response;
        }

        public MessagingManager(string port)
        {
            receivedData = string.Empty;
            serialPort = new SerialPort(port, 19200, Parity.None, 8, StopBits.One);
            serialPort.Handshake = Handshake.RequestToSend;
            serialPort.RtsEnable = true;
            serialPort.DtrEnable = true;
            serialPort.DataReceived +=new SerialDataReceivedEventHandler(DataReceivedHandler);
            serialPort.Open();

            activityRunning = true;
            if (!SendAndWaitForResponse(string.Format("ATE0"))) throw CreateNewException(ReadResponse());
            activityRunning = false;

            pollTimer = new System.Timers.Timer(10000);
            pollTimer.Elapsed += new ElapsedEventHandler(pollTimer_Elapsed);
            pollTimer.Enabled = true;

        }

        void pollTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            activityRunning = true;

            if (!SendAndWaitForResponse(string.Format("AT+CMGF=0"))) throw CreateNewException(ReadResponse());
            if (!SendAndWaitForResponse(string.Format("AT+CPMS=\"SM\""))) throw CreateNewException(ReadResponse());
            if (!SendAndWaitForResponse(string.Format("AT+CMGL=\"ALL\""))) throw CreateNewException(ReadResponse());
            string response = ReadResponse().Replace(NewLine + "OK" + NewLine, "");

            List<SMSMessage> result = ParseMessages(response);

            foreach (SMSMessage message in result)
                OnMessageReceived(message);

            activityRunning = false;
        }

        private List<SMSMessage> ParseMessages(string input)
        {
            List<SMSMessage> messages = new List<SMSMessage>();
            Regex r = new Regex(@"\+CMGL: (\d+),""(.+)"",""(.+)"",(.*),""(.+)""\r\n(.+)\r\n");
            Match m = r.Match(input);
            while (m.Success)
            {
                SMSMessage msg = new SMSMessage();
                msg.Index = int.Parse(m.Groups[1].Value);
                msg.Status = m.Groups[2].Value;
                msg.Sender = m.Groups[3].Value;
                msg.Alphabet = m.Groups[4].Value;
                msg.Sent = m.Groups[5].Value;
                msg.Message = m.Groups[6].Value;
                messages.Add(msg);

                m = m.NextMatch();
            }

            return messages;
        }

        ~MessagingManager()
        {
            serialPort.Close();
        }

        private static void DataReceivedHandler(object sender,SerialDataReceivedEventArgs e)
        {
            SerialPort serialPort = (SerialPort)sender;
            receivedData += serialPort.ReadExisting();
        }

    }
}
